class
and object
expressions.
The generic function you name, and thus, the one you specialize, is the one that is visible in the current module. You can have many different generic functions of the same name in different modules. For information on names and modules, see "Modules" on page 183.
Because method and function definitions are so similar, this section provides only a general summary of the syntax itself. For more details, see the section "Defining Functions" on page 95 of Chapter 5, "Functions, Threads and Pipes." Methods are defined using the following general syntax.
method methodName self
arguments -> body
where:self
holds the current object on which this method operates. (Any other argument name can be used here besides self
; the word self
is used by convention.)
Methods, like functions, can use the return
expression to specify the value the method returns. Without an explicit return, the method returns the value of the last expression evaluated. When in doubt on a return value, consider returning self
, undefined
, or OK
.
object myObj (RootObject)
inst vars a:500
inst methods
method incrementA self inc -> (
self.a := self.a + inc
)
end
class GenericClass (RootObject)
instance methods
method printClass self -> (
print ("I'm an instance of " + (getClassName self))
)
method printMe self -> (
format debug "This is me: %*.\n" self @normal
printClass self
)
end
global myGenericClass := new GenericClass
printMe myGenericClass
This is me: GenericClass@0x1161b7c
"I'm an instance of GenericClass"
class MyLL (Array)
instance methods
method addItemToBeginning self item -> (
addNth self 1 item
format debug "%* added " item @normal
return self
)
end
global myLinkedList := new MyLL
addItemToBeginning myLinkedList 12345
12345 added #(12345) as MyLL
MyClass
, defines three methods: addAllIVs
, which takes no required arguments (except self
) and sums the values of the instance variables a
, b
, and c
.
addEmUp
takes a #rest
argument, and sums all the values of its arguments, then adds the value returned by addAllIVs
.
changeIVs
takes three keyword arguments (incA
, incB
, incC
), for which the default values are all 10, and increments the values of a
, b
, and c
with the appropriate increment.
class MyClass ()
instance vars a,b,c
instance methods
method addAllIVs self -> (
self.a + self.b + self.c
)
method addEmUp self #rest allArgs -> (
local s := 0
for i in allArgs do (s := s + i)
s := s + (addAllIVs self)
)
method changeIVs self #key incA:(10) incB:(10) incC:(10) -> (
self.a := self.a + incA
self.b := self.b + incB
self.c := self.c + incC
print self.a; print self.b; print self.c
return self
)
end
Now that the class and its methods have been defined, here are some examples of its use:
object exmpl (MyClass)
settings a:1, b:5, c:12
end
exmpl.a
1
exmpl.b
5
exmpl.c
12
addAllIVs exmpl
18
addEmUp exmpl 3 8 4 6
39
changeIVs exmpl -- no keywords, defaults are all 10
11
15
22
changeIVs exmpl incA:4 incB:-7
15
8
32
Free methods are methods that can be defined outside the boundaries of a class or object definition. Free methods are useful for adding or redefining method definitions in existing classes or objects without having to redefine the entire class or object.
Free method definitions look similar to regular method definitions, with the addition of a special clause that specifies the class or object to which this method belongs. There are two forms of method definition: one for adding instance methods to an object, and one for adding either class or instance methods to a class.
method methodName self { object object } args -> body
[ class ] method methodName self { class class } args -> bodyIn both forms, methodName is the name of the generic function that invokes this method. If the
class
reserved word is included before the method
reserved word, that method is a class method.
The args part of each free method definition supplies the arguments this method takes (besides self
). These can be positional arguments, rest arguments, or keyword arguments. See Chapter 5, "Functions, Threads and Pipes," for a description of each of these types of arguments.
Finally, the body part of the method definition is the expression, often a compound expression, that the method evaluates when invoked.
The first form adds an instance method to the object specified by object. The expression in braces holds an object, and can be one of the following expressions, where appropriate:
myArray[1]
)
self.x
)
tryThisOut := #(1,2)
method appendSum self {object tryThisOut} n m ->
(append self (n + m); return self)
appendSum tryThisOut 3 4
#(1,2,7)
The second form adds either an instance method or a class method to the class specified by class, which can contain one of the expressions from the list above for object.
class MyClass () end
method jellydonut self {class MyClass} name ->
format debug "%* is not a jellydonut!\n" name @unadorned
i := new MyClass
jellydonut i "cruller"
"cruller is not a jellydonut!"
When adding methods to the core classes, be careful not to override existing methods. That is, avoid defining a method of the same name as one that already exists in that class, particularly initialization (init
and afterInit
) methods. A class may depend on internal behavior defined by those methods; overriding those methods may cause errors in the operation of that class.
As an alternative to overriding existing methods in the ScriptX Core Classes, consider creating a subclass of that class with your own definitions instead.
TwoDShape
defines the draw
method; a subclass of TwoDShape
that re-implements the draw
method is said to override the draw
defined in its superclass. For an example of instance specialization, an instance of TwoDShape
that re-implements the draw
method is said to override the draw
defined in its class.
When you override an existing method, your method must have the same name and positional arguments as the original method, but can have new keyword arguments. In addition, the method can use the functionality provided by superclasses by calling nextMethod
. As such, there are two ways to override an existing method:
nextMethod
.
nextMethod
. Use this approach when the behavior you want is not an addition to the existing behavior. However, some methods (init
, afterInit
) require that you call nextMethod
.
You use nextMethod
to call the overriding method from the body of a method, like this:
method methodName self arguments -> (The
. . . optionally do something here . . .
nextMethod self arguments
. . . optionally do something else here . . .
)
nextMethod
expression passes the call upward through the inheritance hierarchy, calling methods with the same name, so that each superclass can invoke its own implementation of that method on the instance. Another way to look at this is that invoking nextMethod
simply allows you to call the original methods that would have been invoked had you not overridden. Each nextMethod
encountered calls the next method up the chain, hence the term nextMethod
.
Since nextMethod
calls methods in superclasses, you should supply to nextMethod
any arguments that you want those superclasses to handle. For example, since the draw
method in TwoDShape
takes three arguments (self
, surface
, clip
), when you create a subclass of TwoDShape
where you override draw
, you would call nextMethod
with those same arguments:
method draw surface clip -> (
nextMethod self surface clip
)
In another example, the following method definition overrides the append
method for a given object such that the original append
is called only if the item to be appended to this object is an instance of the ImmediateInteger
class. (The append
method is defined in the Sequence
class.) Otherwise, an error message is printed to the debug
stream and the method returns undefined
:
global myArrayOfIntegers := #()
-- override the append method on myArrayOfIntegers
method append self {object myArrayOfIntegers} item -> (
if (getClass item = ImmediateInteger) then (
nextMethod self item
)
else (
format debug "Not an ImmediateInteger: %*\n" item @normal
return undefined
)
)
RootObject
. In this case, nextMethod
calls the next method in depth-first order, as described in "Multiple Inheritance" on page 122. Use getSupers
to see the order in which the methods are called. For example, given a class that inherits from both TwoDShape
and Bounce
, nextMethod
calls the classes in the order shown here:class BouncingShape (TwoDShape, Bounce)
end
getSupers BouncingShape | print
#<Class Substrate:Bounce>
#<Class Substrate:TwoDController>
#<Class Substrate:Controller>
#<Class Substrate:IndirectCollection>
#<Class Substrate:Collection>
#<Class Substrate:TwoDShape>
#<Class Substrate:TwoDPresenter>
#<Class Substrate:Presenter>
#<Class Substrate:RootObject>
This document is part of the ScriptX Language Guide, one of the volumes of the ScriptX Technical Reference Series. ScriptX is developed by the ScriptX Engineering Team at Apple Computer, successor to the Kaleida Engineering Team at Kaleida Labs, Inc.